// $Id: $

/**************************************************************************
 * This file is property of and copyright by the ALICE HLT Project        *
 * ALICE Experiment at CERN, All rights reserved.                         *
 *                                                                        *
 * Primary Authors: Artur Szostak <artursz@iafrica.com>                   *
 *                  for The ALICE HLT Project.                            *
 *                                                                        *
 * Permission to use, copy, modify and distribute this software and its   *
 * documentation strictly for non-commercial purposes is hereby granted   *
 * without fee, provided that the above copyright notice appears in all   *
 * copies and that both the copyright notice and this permission notice   *
 * appear in the supporting documentation. The authors make no claims     *
 * about the suitability of this software for any purpose. It is          *
 * provided "as is" without express or implied warranty.                  *
 **************************************************************************/

/// @file   testAliHLTReadoutList.C
/// @author Artur Szostak <artursz@iafrica.com>
/// @date   8 June 2010
/// @brief  Test program for the AliHLTReadoutList class.
///

#if defined(__CINT__) && (! defined(__MAKECINT__))
#error This macro must be compiled. Try running as testAliHLTReadoutList.C++.
#endif

#if !defined(__CINT__) || defined(__MAKECINT__)
#include "AliHLTDataTypes.h"
#include "AliHLTReadoutList.h"
#include "AliHLTDAQ.h"
#include "TRandom3.h"
#include "TString.h"
#include "Riostream.h"
#include <vector>
#include <algorithm>
#endif

// The detector codes as used by AliHLTReadoutList.
const int kgNumberOfCodes = 21;
const int kgDetCodes[kgNumberOfCodes] = {
	AliHLTReadoutList::kITSSPD,
	AliHLTReadoutList::kITSSDD,
	AliHLTReadoutList::kITSSSD,
	AliHLTReadoutList::kTPC,
	AliHLTReadoutList::kTRD,
	AliHLTReadoutList::kTOF,
	AliHLTReadoutList::kHMPID,
	AliHLTReadoutList::kPHOS,
	AliHLTReadoutList::kCPV,
	AliHLTReadoutList::kPMD,
	AliHLTReadoutList::kMUONTRK,
	AliHLTReadoutList::kMUONTRG,
	AliHLTReadoutList::kFMD,
	AliHLTReadoutList::kT0,
	AliHLTReadoutList::kV0,
	AliHLTReadoutList::kZDC,
	AliHLTReadoutList::kACORDE,
	AliHLTReadoutList::kTRG,
	AliHLTReadoutList::kEMCAL,
	AliHLTReadoutList::kDAQTEST,
	AliHLTReadoutList::kHLT
};
const char* kgDetCodeName[kgNumberOfCodes] = {
	"AliHLTReadoutList::kITSSPD",
	"AliHLTReadoutList::kITSSDD",
	"AliHLTReadoutList::kITSSSD",
	"AliHLTReadoutList::kTPC",
	"AliHLTReadoutList::kTRD",
	"AliHLTReadoutList::kTOF",
	"AliHLTReadoutList::kHMPID",
	"AliHLTReadoutList::kPHOS",
	"AliHLTReadoutList::kCPV",
	"AliHLTReadoutList::kPMD",
	"AliHLTReadoutList::kMUONTRK",
	"AliHLTReadoutList::kMUONTRG",
	"AliHLTReadoutList::kFMD",
	"AliHLTReadoutList::kT0",
	"AliHLTReadoutList::kV0",
	"AliHLTReadoutList::kZDC",
	"AliHLTReadoutList::kACORDE",
	"AliHLTReadoutList::kTRG",
	"AliHLTReadoutList::kEMCAL",
	"AliHLTReadoutList::kDAQTEST",
	"AliHLTReadoutList::kHLT"
};

/**
 * Converts a code to string.
 * \param code  The ID code of the detector. One of AliHLTReadoutList::EDetectorId
 * \returns the code name as a string given a the detector code.
 */
const char* CodeToString(int code)
{
	for (int i = 0; i < kgNumberOfCodes; ++i)
	{
		if (kgDetCodes[i] == code) return kgDetCodeName[i];
	}
	return "UNKNOWN";
}

/**
 * Checks if the basic empty and clear methods work.
 */
bool CheckEmptyAndClear()
{
	AliHLTReadoutList rl;
	if (rl.Empty() != true)
	{
		cerr << "ERROR: AliHLTReadoutList::Empty returns false for an empty readout list." << endl;
		return false;
	}
	
	// Enable all the detectors and check this operation.
	rl.Enable(AliHLTReadoutList::kALLDET);
	for (int i = 0; i < kgNumberOfCodes; ++i)
	{
		if (i == 19) continue; // This is the test DDL. off by default.
		if (not rl.DetectorEnabled(kgDetCodes[i]))
		{
			cerr << "ERROR: AliHLTReadoutList::Enable(AliHLTReadoutList::kALLDET) did not enable for "
				<< CodeToString(kgDetCodes[i]) << "." << endl;
			return false;
		}
	}
	if (rl.DetectorEnabled(AliHLTReadoutList::kDAQTEST))
	{
		cerr << "ERROR: AliHLTReadoutList::Enable(AliHLTReadoutList::kALLDET) enabled bits"
			" for AliHLTReadoutList::kDAQTEST but should not have." << endl;
		return false;
	}
	
	rl.Clear();
	// Fetch the raw bits for the readout list structure and check that they
	// are all zero, since we should have disabled everything in the loop above.
	AliHLTEventDDL bits = rl;
	if (bits.fCount != (unsigned int)gkAliHLTDDLListSize)
	{
		cerr << "ERROR: Typecast operator AliHLTEventDDL () is not"
			" setting the fCount of the structure correctly." << endl;
		return false;
	}
	for (int j = 0; j < gkAliHLTDDLListSize; ++j)
	{
		if (bits.fList[j] != 0x0)
		{
			cerr << "ERROR: Word " << j << " in internal AliHLTReadoutList"
				" bitfield structure is not zero as expected after a"
				" call to AliHLTReadoutList::Clear." << endl;
			return false;
		}
	}
	
	return true;
}

/**
 * Tests enabling and disabling of different detectors.
 */
bool CheckEnablingDisabling()
{
	for (int i = 0; i < 10000; ++i)
	{
		// Get 3 random detector codes.
		int detNum[3] = {
			gRandom->Integer(kgNumberOfCodes),
			gRandom->Integer(kgNumberOfCodes),
			gRandom->Integer(kgNumberOfCodes)
		};
		int code[3] = {
			kgDetCodes[detNum[0]],
			kgDetCodes[detNum[1]],
			kgDetCodes[detNum[2]]
		};
		// make sure the codes are not duplicated.
		while (code[1] == code[0])
		{
			detNum[1] = gRandom->Integer(kgNumberOfCodes);
			code[1] = kgDetCodes[detNum[1]];
		}
		while (code[2] == code[1] or code[2] == code[0])
		{
			detNum[2] = gRandom->Integer(kgNumberOfCodes);
			code[2] = kgDetCodes[detNum[2]];
		}
		
		// Choose the number of codes to use, from 1 to max 3.
		int codeCount = gRandom->Integer(3) + 1;
		
		// Build up the detector code list for the AliHLTReadoutList constructor.
		int totalCode = 0;
		for (int j = 0; j < codeCount; ++j) totalCode |= code[j];
		
		AliHLTReadoutList rl(totalCode);
		if (rl.Empty() == true)
		{
			cerr << "ERROR: AliHLTReadoutList::Empty returns true for a non empty readout list." << endl;
			return false;
		}
		
		// Check that the correct detectors have been enabled and
		// that we can disable a detector correctly.
		for (int j = 0; j < codeCount; ++j)
		{
			if (rl.DetectorEnabled(code[j]) == false)
			{
				cerr << "ERROR: Detector was not enabled for "
					<< CodeToString(code[j]) << " by constructor." << endl;
				return false;
			}
			if (rl.DetectorDisabled(code[j]) == true)
			{
				cerr << "ERROR: DetectorDisabled returned and incorrect result"
					" when detectors enabled for "
					<< CodeToString(code[j]) << " by constructor." << endl;
				return false;
			}
			
			// Also check each bit individualy according to AliHLTDAQ values.
			int det = detNum[j];
			int maxddls = AliHLTDAQ::NumberOfDdls(det);
			for (int ddlindex = 0; ddlindex < maxddls; ++ddlindex)
			{
				int ddlid = AliHLTDAQ::DdlIDOffset(det) | (ddlindex & 0xFF);
				if (rl.IsDDLDisabled(ddlid))
				{
					cerr << "ERROR: Bit not set for DDL " << ddlid
						<< ", even though detector "
						<< AliHLTDAQ::OnlineName(det)
						<< " was enabled." << endl;
					return false;
				}
			}
			
			rl.Disable(code[j]);
			if (rl.DetectorEnabled(code[j]) == true)
			{
				cerr << "ERROR: AliHLTReadoutList::Disable(x) is not working for x = "
					<< CodeToString(code[j]) << "." << endl;
				return false;
			}
			if (rl.DetectorDisabled(code[j]) == false)
			{
				cerr << "ERROR: DetectorDisabled returned and incorrect result"
					" when calling AliHLTReadoutList::Disable(x) for "
					<< CodeToString(code[j]) << "." << endl;
				return false;
			}
		}
		
		// Fetch the raw bits for the readout list structure and check that they
		// are all zero, since we should have disabled everything in the loop above.
		AliHLTEventDDL bits = rl;
		for (int j = 0; j < gkAliHLTDDLListSize; ++j)
		{
			if (bits.fList[j] != 0x0)
			{
				cerr << "ERROR: Word " << j << " in internal AliHLTReadoutList"
					" bitfield structure is not zero as expected." << endl;
				return false;
			}
		}
	}
	return true;
}

/**
 * Tests enabling and disabling of different DDLs.
 */
bool CheckEnablingDisablingDDLs()
{
	for (int i = 0; i < 10000; ++i)
	{
		// Get random DDL IDs that are each unique.
		std::vector<int> ddls;
		int ddlCount = gRandom->Integer(100) + 1;
		for (int j = 0; j < ddlCount; ++j)
		{
			int ddlid = -1;
			do
			{
				int det = gRandom->Integer(AliHLTDAQ::NumberOfDetectors());
				int maxddls = AliHLTDAQ::NumberOfDdls(det);
				// The following is a special check since AliDAQ could be the newer version.
				// det = 18 is for EMCAL and gkAliHLTDDLListSize = 30 indicates old version
				// of AliHLTEventDDL before EMCAL expantion with DCAL.
				if (det == 18 and gkAliHLTDDLListSize == 30 and maxddls > 24) maxddls = 24;
				int ddlindex = gRandom->Integer(maxddls);
				ddlid = AliHLTDAQ::DdlID(det, ddlindex);
				if (std::find(ddls.begin(), ddls.end(), ddlid) != ddls.end()) ddlid = -1;
			}
			while (ddlid == -1);
			ddls.push_back(ddlid);
		}
		
		// Build up the enable list for the AliHLTReadoutList constructor.
		TString enableList;
		for (size_t j = 0; j < ddls.size(); ++j)
		{
			char num[32];
			sprintf(num, "%d", ddls[j]);
			enableList += " ";
			enableList += num;
		}
		
		AliHLTReadoutList rl(enableList.Data());
		if (rl.Empty() == true)
		{
			cerr << "ERROR: AliHLTReadoutList::Empty returns true for a non empty readout list." << endl;
			return false;
		}
		
		// Check that the correct DDLs have been enabled and
		// that we can disable a DDL correctly.
		for (size_t j = 0; j < ddls.size(); ++j)
		{
			if (rl.IsDDLEnabled(ddls[j]) == false)
			{
				cerr << "ERROR: DDL " << ddls[j] << " was not enabled by constructor." << endl;
				return false;
			}
			rl.DisableDDLBit(ddls[j]);
			if (rl.IsDDLDisabled(ddls[j]) == false)
			{
				cerr << "ERROR: AliHLTReadoutList::DisableDDLBit(x) is not working for x = "
					<< ddls[j] << "." << endl;
				return false;
			}
		}
		
		// Fetch the raw bits for the readout list structure and check that they
		// are all zero, since we should have disabled everything in the loop above.
		AliHLTEventDDL bits = rl;
		for (int j = 0; j < gkAliHLTDDLListSize; ++j)
		{
			if (bits.fList[j] != 0x0)
			{
				cerr << "ERROR: Word " << j << " in internal AliHLTReadoutList"
					" bitfield structure is not zero as expected." << endl;
				return false;
			}
		}
	}
	return true;
}

/**
 * Tests if using incorrect DDL IDs returns zero or is ignored as expected.
 */
bool CheckIncorrectIDs()
{
	for (int i = 0; i < 1000000; ++i)
	{
		// Get random DDL ID outside the valid range.
		int ddlid = -1;
		int det = gRandom->Integer(AliHLTDAQ::NumberOfDetectors()+1);
		if (det != AliHLTDAQ::NumberOfDetectors())
		{
			int maxddls = AliHLTDAQ::NumberOfDdls(det);
			int ddlindex = gRandom->Integer(0xFF - maxddls) + maxddls;
			ddlid = AliHLTDAQ::DdlIDOffset(det) | (ddlindex & 0xFF);
		}
		else
		{
			det = gRandom->Integer(11) + 20;
			if (det == 30) det = 31;
			int ddlindex = gRandom->Integer(0xFF);
			ddlid = (det << 8) | (ddlindex & 0xFF);
		}
		
		AliHLTReadoutList rl;
		if (rl.GetDDLBit(ddlid) != kFALSE)
		{
			cerr << "ERROR: Received a non zero result for invalid DDL " << ddlid << "." << endl;
			return false;
		}
		AliHLTEventDDL before = rl;
		rl.EnableDDLBit(ddlid);
		AliHLTEventDDL after = rl;
		if (memcmp(&before, &after, sizeof(AliHLTEventDDL)) != 0)
		{
			cerr << "ERROR: Modified AliHLTReadoutList structure using an invalid DDL "
				<< ddlid << "." << endl;
			cerr << "========== Dump of bits before modification ==========" << endl;
			for (unsigned int j = 0; j < before.fCount; ++j)
			{
				cerr << "[word " << dec << j << "] = " << hex << before.fList[j] << dec << endl;
			}
			cerr << "========== Dump of bits after modification ==========" << endl;
			for (unsigned int j = 0; j < after.fCount; ++j)
			{
				cerr << "[word " << dec << j << "] = " << hex << after.fList[j] << dec << endl;
			}
			return false;
		}
	}
	return true;
}

/**
 * Tests the mapping of the AliHLTReadoutList::GetFirstWord, AliHLTReadoutList::GetWordCount
 * and AliHLTReadoutList::GetDetectorFromWord methods.
 */
bool CheckWordIndexAndCount()
{
	int wordCovered[gkAliHLTDDLListSize];
	for (int j = 0; j < gkAliHLTDDLListSize; ++j) wordCovered[j] = 0;
	
	for (int i = 0; i < kgNumberOfCodes; ++i)
	{
		AliHLTReadoutList rl;
		Int_t firstword = rl.GetFirstWord((AliHLTReadoutList::EDetectorId)kgDetCodes[i]);
		if (firstword < 0 or gkAliHLTDDLListSize-1 < firstword)
		{
			cerr << "ERROR: AliHLTReadoutList::GetFirstWord(" << kgDetCodeName[i]
				<< ") returns " << firstword
				<< ", which is outside the valid range of [0.." << gkAliHLTDDLListSize-1
				<< "]." << endl;
			return false;
		}
		
		Int_t lastword = firstword + rl.GetWordCount((AliHLTReadoutList::EDetectorId)kgDetCodes[i]);
		if (lastword < 1 or gkAliHLTDDLListSize < lastword)
		{
			cerr << "ERROR: The sum AliHLTReadoutList::GetFirstWord(" << kgDetCodeName[i]
				<< ") + AliHLTReadoutList::GetWordCount(" << kgDetCodeName[i]
				<< ") gives " << lastword
				<< ", which is outside the valid range of [1.." << gkAliHLTDDLListSize
				<< "]." << endl;
			return false;
		}
		
		for (int j = firstword; j < lastword; ++j)
		{
			if (wordCovered[j] == 1)
			{
				cerr << "ERROR: The combination of AliHLTReadoutList::GetWordCount(" << kgDetCodeName[i]
					<< ") and AliHLTReadoutList::GetWordCount(" << kgDetCodeName[i]
					<< ") overlaps with previous detectors. Check the mapping in these functions."
					<< endl;
				return false;
			}
			wordCovered[j] = 1;
		}
		
		Int_t maxddls = AliHLTDAQ::NumberOfDdls(i);
		Int_t ddlid = AliHLTDAQ::DdlIDOffset(i) | (gRandom->Integer(maxddls) & 0xFF);
		rl.EnableDDLBit(ddlid);
		if (rl.GetFirstUsedDetector() != kgDetCodes[i])
		{
			cerr << "ERROR: AliHLTReadoutList::GetFirstUsedDetector() did not return the correct value of"
				<< kgDetCodeName[i] << " after calling AliHLTReadoutList::EnableDDLBit("
				<< ddlid << ")." << endl;
			return false;
		}
		if (rl.GetFirstUsedDetector((AliHLTReadoutList::EDetectorId)kgDetCodes[i]) != AliHLTReadoutList::kNoDetector)
		{
			cerr << "ERROR: AliHLTReadoutList::GetFirstUsedDetector(" << kgDetCodeName[i]
				<< "+1) did not return the correct value of AliHLTReadoutList::kNoDetector"
				   " after calling AliHLTReadoutList::EnableDDLBit("
				<< ddlid << ")." << endl;
			return false;
		}
	}
	
	for (int j = 0; j < gkAliHLTDDLListSize; ++j)
	{
		if (wordCovered[j] == 0)
		{
			cerr << "ERROR: The functions AliHLTReadoutList::GetWordCount"
				" and AliHLTReadoutList::GetWordCount do not fully cover"
				" all DDL readout list words." << endl;
			return false;
		}
	}
	
	// Check the mapping of GetDetectorFromWord
	for (int j = 0; j < gkAliHLTDDLListSize; ++j)
	{
		AliHLTReadoutList::EDetectorId det = AliHLTReadoutList::GetDetectorFromWord(j);
		Int_t firstword = AliHLTReadoutList::GetFirstWord(det);
		Int_t lastword = firstword + AliHLTReadoutList::GetWordCount(det);
		if (not (firstword <= j and j < lastword))
		{
			cerr << "ERROR: The function AliHLTReadoutList::GetDetectorFromWord returns "
				<< AliHLTReadoutList::DetectorIdToString(det)
				<< " for word " << j
				<< " but the GetFirstWord and GetWordCount methods indicate a different range."
				<< " The mapping is probably incorrect." << endl;
			return false;
		}
	}
	
	return true;
}

/**
 * Runs the unit test for the AliHLTReadoutList class.
 * \returns true if the class passed the test and false otherwise.
 */
bool testAliHLTReadoutList()
{
	gRandom->SetSeed(123);
	
	if (AliHLTDAQ::NumberOfDetectors() != kgNumberOfCodes)
	{
		cerr << "ERROR: The testAliHLTReadoutList.C macro needs to be updated"
			" or something went really wrong."
			" The number of detectors reported by AliHLTDAQ is not "
			<< kgNumberOfCodes << ", as expected, but "
			<< AliHLTDAQ::NumberOfDetectors() << "." << endl;
		return false;
	}
	
	if (not CheckEmptyAndClear()) return false;
	if (not CheckEnablingDisabling()) return false;
	if (not CheckEnablingDisablingDDLs()) return false;
	if (not CheckIncorrectIDs()) return false;
	if (not CheckWordIndexAndCount()) return false;
	
	return true;
}

#ifndef __MAKECINT__

int main(int /*argc*/, const char** /*argv*/)
{
	bool resultOk = testAliHLTReadoutList();
	if (not resultOk) return 1;
	return 0;
}

#endif // __MAKECINT__
